// Copyright Contributors to the Open Shading Language project.
// SPDX-License-Identifier: BSD-3-Clause
// https://github.com/AcademySoftwareFoundation/OpenShadingLanguage


/////////////////////////////////////////////////////////////////////////
/// \file
///
/// Shader interpreter implementation of texture operations.
///
/////////////////////////////////////////////////////////////////////////

#include <OpenImageIO/fmath.h>
#include <OpenImageIO/simd.h>

#include <cmath>
#include <iostream>

#include "oslexec_pvt.h"
#include <OSL/dual.h>


OSL_NAMESPACE_ENTER
namespace pvt {


// Utility: retrieve a pointer to the ShadingContext's texture options
// struct, also re-initialize its contents.
OSL_SHADEOP void*
osl_get_texture_options(void* sg_)
{
    ShaderGlobals* sg = (ShaderGlobals*)sg_;
    TextureOpt* opt   = sg->context->texture_options_ptr();
    new (opt) TextureOpt;
    return opt;
}


OSL_SHADEOP void
osl_texture_set_firstchannel(void* opt, int x)
{
    ((TextureOpt*)opt)->firstchannel = x;
}

static TextureOpt::Wrap
decode_wrapmode(ustringhash_pod name_)
{
    ustringhash name_hash = ustringhash_from(name_);
#ifdef OIIO_TEXTURESYSTEM_SUPPORTS_DECODE_BY_USTRINGHASH
    return OIIO::TextureOpt::decode_wrapmode(name_hash);
#else
    ustring name = ustring_from(name_hash);
    return OIIO::TextureOpt::decode_wrapmode(name);
#endif
}

OSL_SHADEOP int
osl_texture_decode_wrapmode(ustringhash_pod name_)
{
    return decode_wrapmode(name_);
}

OSL_SHADEOP void
osl_texture_set_swrap(void* opt, ustringhash_pod x_)
{
    ((TextureOpt*)opt)->swrap = decode_wrapmode(x_);
}

OSL_SHADEOP void
osl_texture_set_twrap(void* opt, ustringhash_pod x_)
{
    ((TextureOpt*)opt)->twrap = decode_wrapmode(x_);
}

OSL_SHADEOP void
osl_texture_set_rwrap(void* opt, ustringhash_pod x_)
{
    ((TextureOpt*)opt)->rwrap = decode_wrapmode(x_);
}

OSL_SHADEOP void
osl_texture_set_stwrap(void* opt, ustringhash_pod x_)
{
    TextureOpt::Wrap code     = decode_wrapmode(x_);
    ((TextureOpt*)opt)->swrap = code;
    ((TextureOpt*)opt)->twrap = code;
}

OSL_SHADEOP void
osl_texture_set_swrap_code(void* opt, int mode)
{
    ((TextureOpt*)opt)->swrap = (TextureOpt::Wrap)mode;
}

OSL_SHADEOP void
osl_texture_set_twrap_code(void* opt, int mode)
{
    ((TextureOpt*)opt)->twrap = (TextureOpt::Wrap)mode;
}

OSL_SHADEOP void
osl_texture_set_rwrap_code(void* opt, int mode)
{
    ((TextureOpt*)opt)->rwrap = (TextureOpt::Wrap)mode;
}

OSL_SHADEOP void
osl_texture_set_stwrap_code(void* opt, int mode)
{
    ((TextureOpt*)opt)->swrap = (TextureOpt::Wrap)mode;
    ((TextureOpt*)opt)->twrap = (TextureOpt::Wrap)mode;
}

OSL_SHADEOP void
osl_texture_set_sblur(void* opt, float x)
{
    ((TextureOpt*)opt)->sblur = x;
}

OSL_SHADEOP void
osl_texture_set_tblur(void* opt, float x)
{
    ((TextureOpt*)opt)->tblur = x;
}

OSL_SHADEOP void
osl_texture_set_rblur(void* opt, float x)
{
    ((TextureOpt*)opt)->rblur = x;
}

OSL_SHADEOP void
osl_texture_set_stblur(void* opt, float x)
{
    ((TextureOpt*)opt)->sblur = x;
    ((TextureOpt*)opt)->tblur = x;
}

OSL_SHADEOP void
osl_texture_set_swidth(void* opt, float x)
{
    ((TextureOpt*)opt)->swidth = x;
}

OSL_SHADEOP void
osl_texture_set_twidth(void* opt, float x)
{
    ((TextureOpt*)opt)->twidth = x;
}

OSL_SHADEOP void
osl_texture_set_rwidth(void* opt, float x)
{
    ((TextureOpt*)opt)->rwidth = x;
}

OSL_SHADEOP void
osl_texture_set_stwidth(void* opt, float x)
{
    ((TextureOpt*)opt)->swidth = x;
    ((TextureOpt*)opt)->twidth = x;
}

OSL_SHADEOP void
osl_texture_set_fill(void* opt, float x)
{
    ((TextureOpt*)opt)->fill = x;
}

OSL_SHADEOP void
osl_texture_set_time(void* opt, float x)
{
    ((TextureOpt*)opt)->time = x;
}

OSL_SHADEOP int
osl_texture_decode_interpmode(ustringhash_pod name_)
{
    ustringhash name_hash = ustringhash_from(name_);
    return tex_interp_to_code(name_hash);
}

OSL_SHADEOP void
osl_texture_set_interp(void* opt, ustringhash_pod modename_)
{
    ustringhash modename_hash = ustringhash_from(modename_);
    ustring modename          = ustring_from(modename_hash);
    int mode                  = tex_interp_to_code(modename);
    if (mode >= 0)
        ((TextureOpt*)opt)->interpmode = (TextureOpt::InterpMode)mode;
}

OSL_SHADEOP void
osl_texture_set_interp_code(void* opt, int mode)
{
    ((TextureOpt*)opt)->interpmode = (TextureOpt::InterpMode)mode;
}

OSL_SHADEOP void
osl_texture_set_subimage(void* opt, int subimage)
{
    ((TextureOpt*)opt)->subimage = subimage;
}


OSL_SHADEOP void
osl_texture_set_subimagename(void* opt, ustringhash_pod subimagename_)
{
    ustringhash subimagename_hash    = ustringhash_from(subimagename_);
    ustring subimagename             = ustring_from(subimagename_hash);
    ((TextureOpt*)opt)->subimagename = subimagename;
}

OSL_SHADEOP void
osl_texture_set_missingcolor_arena(void* opt, const void* missing)
{
    ((TextureOpt*)opt)->missingcolor = (const float*)missing;
}

OSL_SHADEOP void
osl_texture_set_missingcolor_alpha(void* opt, int alphaindex,
                                   float missingalpha)
{
    float* m = (float*)((TextureOpt*)opt)->missingcolor;
    if (m)
        m[alphaindex] = missingalpha;
}



OSL_SHADEOP int
osl_texture(void* sg_, ustringhash_pod name_, void* handle, void* opt_, float s,
            float t, float dsdx, float dtdx, float dsdy, float dtdy, int chans,
            void* result, void* dresultdx, void* dresultdy, void* alpha,
            void* dalphadx, void* dalphady, ustringhash_pod* errormessage)
{
    ShaderGlobals* sg = (ShaderGlobals*)sg_;
    TextureOpt* opt   = (TextureOpt*)opt_;
    bool derivs       = (dresultdx || dalphadx);
    // It's actually faster to ask for 4 channels (even if we need fewer)
    // and ensure that they're being put in aligned memory.
    OIIO::simd::vfloat4 result_simd, dresultds_simd, dresultdt_simd;
    ustringhash em;
    ustringhash name = ustringhash_from(name_);
    bool ok = sg->renderer->texture(name, (TextureSystem::TextureHandle*)handle,
                                    sg->context->texture_thread_info(), *opt,
                                    sg, s, t, dsdx, dtdx, dsdy, dtdy, 4,
                                    (float*)&result_simd,
                                    derivs ? (float*)&dresultds_simd : NULL,
                                    derivs ? (float*)&dresultdt_simd : NULL,
                                    errormessage ? &em : nullptr);

    for (int i = 0; i < chans; ++i)
        ((float*)result)[i] = result_simd[i];
    if (alpha)
        ((float*)alpha)[0] = result_simd[chans];

    // Correct our st texture space gradients into xy-space gradients
    if (derivs) {
        OSL_DASSERT((dresultdx == nullptr) == (dresultdy == nullptr));
        OSL_DASSERT((dalphadx == nullptr) == (dalphady == nullptr));
        OIIO::simd::vfloat4 dresultdx_simd = dresultds_simd * dsdx
                                             + dresultdt_simd * dtdx;
        OIIO::simd::vfloat4 dresultdy_simd = dresultds_simd * dsdy
                                             + dresultdt_simd * dtdy;
        if (dresultdx) {
            for (int i = 0; i < chans; ++i)
                ((float*)dresultdx)[i] = dresultdx_simd[i];
            for (int i = 0; i < chans; ++i)
                ((float*)dresultdy)[i] = dresultdy_simd[i];
        }
        if (dalphadx) {
            ((float*)dalphadx)[0] = dresultdx_simd[chans];
            ((float*)dalphady)[0] = dresultdy_simd[chans];
        }
    }

    if (errormessage)
        *errormessage = ok ? ustringhash {}.hash() : em.hash();
    return ok;
}



OSL_SHADEOP int
osl_texture3d(void* sg_, ustringhash_pod name_, void* handle, void* opt_,
              void* P_, void* dPdx_, void* dPdy_, void* dPdz_, int chans,
              void* result, void* dresultdx, void* dresultdy, void* alpha,
              void* dalphadx, void* dalphady, ustringhash_pod* errormessage)
{
    const Vec3& P(*(Vec3*)P_);
    const Vec3& dPdx(*(Vec3*)dPdx_);
    const Vec3& dPdy(*(Vec3*)dPdy_);
    Vec3 dPdz(0.0f);
    if (dPdz_ != nullptr) {
        dPdz = (*(Vec3*)dPdz_);
    }
    ShaderGlobals* sg = (ShaderGlobals*)sg_;
    TextureOpt* opt   = (TextureOpt*)opt_;
    bool derivs       = (dresultdx != NULL || dalphadx != NULL);
    // It's actually faster to ask for 4 channels (even if we need fewer)
    // and ensure that they're being put in aligned memory.
    OIIO::simd::vfloat4 result_simd, dresultds_simd, dresultdt_simd,
        dresultdr_simd;
    ustringhash em;
    ustringhash name = ustringhash_from(name_);
    bool ok
        = sg->renderer->texture3d(name, (TextureSystem::TextureHandle*)handle,
                                  sg->context->texture_thread_info(), *opt, sg,
                                  P, dPdx, dPdy, dPdz, 4, (float*)&result_simd,
                                  derivs ? (float*)&dresultds_simd : nullptr,
                                  derivs ? (float*)&dresultdt_simd : nullptr,
                                  derivs ? (float*)&dresultdr_simd : nullptr,
                                  errormessage ? &em : nullptr);

    for (int i = 0; i < chans; ++i)
        ((float*)result)[i] = result_simd[i];
    if (alpha)
        ((float*)alpha)[0] = result_simd[chans];

    // Correct our str texture space gradients into xyz-space gradients
    if (derivs) {
        OIIO::simd::vfloat4 dresultdx_simd = dresultds_simd * dPdx.x
                                             + dresultdt_simd * dPdx.y
                                             + dresultdr_simd * dPdx.z;
        OIIO::simd::vfloat4 dresultdy_simd = dresultds_simd * dPdy.x
                                             + dresultdt_simd * dPdy.y
                                             + dresultdr_simd * dPdy.z;
        if (dresultdx) {
            for (int i = 0; i < chans; ++i)
                ((float*)dresultdx)[i] = dresultdx_simd[i];
            for (int i = 0; i < chans; ++i)
                ((float*)dresultdy)[i] = dresultdy_simd[i];
        }
        if (dalphadx) {
            ((float*)dalphadx)[0] = dresultdx_simd[chans];
            ((float*)dalphady)[0] = dresultdy_simd[chans];
        }
    }

    if (errormessage)
        *errormessage = ok ? ustringhash {}.hash() : em.hash();
    return ok;
}



OSL_SHADEOP int
osl_environment(void* sg_, ustringhash_pod name_, void* handle, void* opt_,
                void* R_, void* dRdx_, void* dRdy_, int chans, void* result,
                void* dresultdx, void* dresultdy, void* alpha, void* dalphadx,
                void* dalphady, ustringhash_pod* errormessage)
{
    const Vec3& R(*(Vec3*)R_);
    const Vec3& dRdx(*(Vec3*)dRdx_);
    const Vec3& dRdy(*(Vec3*)dRdy_);
    ShaderGlobals* sg = (ShaderGlobals*)sg_;
    TextureOpt* opt   = (TextureOpt*)opt_;
    // It's actually faster to ask for 4 channels (even if we need fewer)
    // and ensure that they're being put in aligned memory.
    OIIO::simd::vfloat4 local_result;
    ustringhash em;
    ustringhash name = ustringhash_from(name_);
    bool ok
        = sg->renderer->environment(name, (TextureSystem::TextureHandle*)handle,
                                    sg->context->texture_thread_info(), *opt,
                                    sg, R, dRdx, dRdy, 4, (float*)&local_result,
                                    NULL, NULL, errormessage ? &em : nullptr);

    for (int i = 0; i < chans; ++i)
        ((float*)result)[i] = local_result[i];

    // For now, just zero out the result derivatives.  If somebody needs
    // derivatives of environment lookups, we'll fix it.  The reason
    // that this is a pain is that OIIO's environment call (unwisely?)
    // returns the st gradients, but we want the xy gradients, which is
    // tricky because we (this function you're reading) don't know which
    // projection is used to generate st from R.  Ugh.  Sweep under the
    // rug for a day when somebody is really asking for it.
    if (dresultdx) {
        for (int i = 0; i < chans; ++i)
            ((float*)dresultdx)[i] = 0.0f;
        for (int i = 0; i < chans; ++i)
            ((float*)dresultdy)[i] = 0.0f;
    }
    if (alpha) {
        ((float*)alpha)[0] = local_result[chans];
        // Zero out the alpha derivatives, for the same reason as above.
        if (dalphadx)
            ((float*)dalphadx)[0] = 0.0f;
        if (dalphady)
            ((float*)dalphady)[0] = 0.0f;
    }

    if (errormessage)
        *errormessage = ok ? ustringhash {}.hash() : em.hash();
    return ok;
}



OSL_SHADEOP int
osl_get_textureinfo(void* sg_, ustringhash_pod name_, void* handle,
                    ustringhash_pod dataname_, int type, int arraylen,
                    int aggregate, void* data, ustringhash_pod* errormessage)
{
    // recreate TypeDesc
    TypeDesc typedesc;
    typedesc.basetype  = type;
    typedesc.arraylen  = arraylen;
    typedesc.aggregate = aggregate;

    ShaderGlobals* sg = (ShaderGlobals*)sg_;

    ustringhash em;
    ustringhash name     = ustringhash_from(name_);
    ustringhash dataname = ustringhash_from(dataname_);
    bool ok              = sg->renderer->get_texture_info(
        name, (RendererServices::TextureHandle*)handle,
        sg->context->texture_thread_info(), sg, 0 /*FIXME-ptex*/, dataname,
        typedesc, data, errormessage ? &em : nullptr);
    if (errormessage)
        *errormessage = ok ? ustringhash {}.hash() : em.hash();
    return ok;
}



OSL_SHADEOP int
osl_get_textureinfo_st(void* sg_, ustringhash_pod name_, void* handle, float s,
                       float t, ustringhash_pod dataname_, int type,
                       int arraylen, int aggregate, void* data,
                       ustringhash_pod* errormessage)
{
    // recreate TypeDesc
    TypeDesc typedesc;
    typedesc.basetype  = type;
    typedesc.arraylen  = arraylen;
    typedesc.aggregate = aggregate;

    ShaderGlobals* sg = (ShaderGlobals*)sg_;

    ustringhash em;
    ustringhash name     = ustringhash_from(name_);
    ustringhash dataname = ustringhash_from(dataname_);
    bool ok              = sg->renderer->get_texture_info(
        name, (RendererServices::TextureHandle*)handle, s, t,
        sg->context->texture_thread_info(), sg, 0 /*FIXME-ptex*/, dataname,
        typedesc, data, errormessage ? &em : nullptr);
    if (errormessage)
        *errormessage = ok ? ustringhash {}.hash() : em.hash();
    return ok;
}



// Trace

// Utility: retrieve a pointer to the ShadingContext's trace options
// struct, also re-initialize its contents.
OSL_SHADEOP void*
osl_get_trace_options(void* sg_)
{
    ShaderGlobals* sg               = (ShaderGlobals*)sg_;
    RendererServices::TraceOpt* opt = sg->context->trace_options_ptr();
    new (opt) RendererServices::TraceOpt;
    return opt;
}

OSL_SHADEOP void
osl_trace_set_mindist(void* opt, float x)
{
    ((RendererServices::TraceOpt*)opt)->mindist = x;
}

OSL_SHADEOP void
osl_trace_set_maxdist(void* opt, float x)
{
    ((RendererServices::TraceOpt*)opt)->maxdist = x;
}

OSL_SHADEOP void
osl_trace_set_shade(void* opt, int x)
{
    ((RendererServices::TraceOpt*)opt)->shade = x;
}


OSL_SHADEOP void
osl_trace_set_traceset(void* opt, const ustringhash_pod x)
{
    ((RendererServices::TraceOpt*)opt)->traceset = ustring_from(x);
}


OSL_SHADEOP int
osl_trace(void* sg_, void* opt_, void* Pos_, void* dPosdx_, void* dPosdy_,
          void* Dir_, void* dDirdx_, void* dDirdy_)
{
    ShaderGlobals* sg               = (ShaderGlobals*)sg_;
    RendererServices::TraceOpt* opt = (RendererServices::TraceOpt*)opt_;
    static const Vec3 Zero(0.0f, 0.0f, 0.0f);
    const Vec3* Pos    = (Vec3*)Pos_;
    const Vec3* dPosdx = dPosdx_ ? (Vec3*)dPosdx_ : &Zero;
    const Vec3* dPosdy = dPosdy_ ? (Vec3*)dPosdy_ : &Zero;
    const Vec3* Dir    = (Vec3*)Dir_;
    const Vec3* dDirdx = dDirdx_ ? (Vec3*)dDirdx_ : &Zero;
    const Vec3* dDirdy = dDirdy_ ? (Vec3*)dDirdy_ : &Zero;
    return sg->renderer->trace(*opt, sg, *Pos, *dPosdx, *dPosdy, *Dir, *dDirdx,
                               *dDirdy);
}


}  // namespace pvt
OSL_NAMESPACE_EXIT
